/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.topplusvision.topfaceplus.exif;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;
import android.util.SparseIntArray;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;

/**
 * This class provides methods and constants for reading and writing jpeg file
 * metadata. It contains a collection of ExifTags, and a collection of
 * definitions for creating valid ExifTags. The collection of ExifTags can be
 * updated by: reading new ones from a file, deleting or adding existing ones,
 * or building new ExifTags from a tag definition. These ExifTags can be written
 * to a valid jpeg image as exif metadata.
 * <p/>
 * Each ExifTag has a tag ID (TID) and is stored in a specific image file
 * directory (IFD) as specified by the exif standard. A tag definition can be
 * looked up with a constant that is a combination of TID and IFD. This
 * definition has information about the type, number of components, and valid
 * IFDs for a tag.
 *
 * @see ExifTag
 */
public class ExifInterface {
    public static final int TAG_NULL = -1;
    public static final int IFD_NULL = -1;
    public static final int DEFINITION_NULL = 0;
    /**
     * Tag constants for Jeita EXIF 2.2
     */

    // IFD 0
    public static final int TAG_IMAGE_WIDTH = defineTag(IfdId.TYPE_IFD_0, (short) 0x0100);
    public static final int TAG_IMAGE_LENGTH = defineTag(IfdId.TYPE_IFD_0, (short) 0x0101); // Image height
    public static final int TAG_BITS_PER_SAMPLE = defineTag(IfdId.TYPE_IFD_0, (short) 0x0102);
    /**
     * Value is unsigned int.<br />
     * (Read only tag) The compression scheme used for the image data. When a primary image is JPEG compressed, this designation is
     * not necessary and is omitted. When thumbnails use JPEG compression, this tag value is set to 6.
     * <ul>
     * <li>1 = uncompressed</li>
     * <li>6 = JPEG compression (thumbnails only)</li>
     * <li>Other = reserved</li>
     */
    public static final int TAG_COMPRESSION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0103);
    public static final int TAG_PHOTOMETRIC_INTERPRETATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0106);
    public static final int TAG_IMAGE_DESCRIPTION = defineTag(IfdId.TYPE_IFD_0, (short) 0x010E);
    /**
     * Value is ascii string<br />
     * The manufacturer of the recording equipment. This is the manufacturer of the DSC, scanner, video digitizer or other equipment
     * that generated the image. When the field is left blank, it is treated as unknown.
     */
    public static final int TAG_MAKE = defineTag(IfdId.TYPE_IFD_0, (short) 0x010F);
    /**
     * Value is ascii string<br />
     * The model name or model number of the equipment. This is the model name of number of the DSC, scanner, video digitizer or
     * other equipment that generated the image. When the field is left blank, it is treated as unknown.
     */
    public static final int TAG_MODEL = defineTag(IfdId.TYPE_IFD_0, (short) 0x0110);
    public static final int TAG_STRIP_OFFSETS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0111);
    /**
     * Value is int<br />
     * The orientation of the camera relative to the scene, when the image was captured. The start point of stored data is:
     * <ul>
     * <li>'0' undefined</li>
     * <li>'1' normal</li>
     * <li>'2' flip horizontal</li>
     * <li>'3' rotate 180</li>
     * <li>'4' flip vertical</li>
     * <li>'5' transpose, flipped about top-left <--> bottom-right axis</li>
     * <li>'6' rotate 90 cw</li>
     * <li>'7' transverse, flipped about top-right <--> bottom-left axis</li>
     * <li>'8' rotate 270</li>
     * <li>'9' undefined</li>
     * </ul>
     */
    public static final int TAG_ORIENTATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x0112);
    public static final int TAG_SAMPLES_PER_PIXEL = defineTag(IfdId.TYPE_IFD_0, (short) 0x0115);
    public static final int TAG_ROWS_PER_STRIP = defineTag(IfdId.TYPE_IFD_0, (short) 0x0116);
    public static final int TAG_STRIP_BYTE_COUNTS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0117);
    public static final int TAG_INTEROP_VERSION = defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 0x0002);
    /**
     * Value is unsigned double.<br />
     * Display/Print resolution of image. Large number of digicam uses 1/72inch, but it has no mean because personal computer doesn't
     * use this value to display/print out.
     */
    public static final int TAG_X_RESOLUTION = defineTag(IfdId.TYPE_IFD_0, (short) 0x011A);
    /**
     * @see #TAG_X_RESOLUTION
     */
    public static final int TAG_Y_RESOLUTION = defineTag(IfdId.TYPE_IFD_0, (short) 0x011B);
    public static final int TAG_PLANAR_CONFIGURATION = defineTag(IfdId.TYPE_IFD_0, (short) 0x011C);
    /**
     * Value is unsigned int.<br />
     * Unit of XResolution(0x011a)/YResolution(0x011b)
     * <ul>
     * <li>'1' means no-unit ( use inch )</li>
     * <li>'2' inch</li>
     * <li>'3' centimeter</li>
     * <li>'4' millimeter</li>
     * <li>'5' micrometer</li>
     * </ul>
     */
    public static final int TAG_RESOLUTION_UNIT = defineTag(IfdId.TYPE_IFD_0, (short) 0x0128);
    public static final int TAG_TRANSFER_FUNCTION = defineTag(IfdId.TYPE_IFD_0, (short) 0x012D);
    /**
     * Value is ascii string<br />
     * Shows firmware(internal software of digicam) version number.
     */
    public static final int TAG_SOFTWARE = defineTag(IfdId.TYPE_IFD_0, (short) 0x0131);
    /**
     * Value is ascii string (20)<br />
     * Date/Time of image was last modified. Data format is "YYYY:MM:DD HH:MM:SS"+0x00, total 20bytes. In usual, it has the same
     * value of DateTimeOriginal(0x9003)
     */
    public static final int TAG_DATE_TIME = defineTag(IfdId.TYPE_IFD_0, (short) 0x0132);
    /**
     * Vallue is ascii String<br />
     * This tag records the name of the camera owner, photographer or image creator. The detailed format is not specified, but it is
     * recommended that the information be written as in the example below for ease of Interoperability. When the field is left
     * blank, it is treated as unknown.
     */
    public static final int TAG_ARTIST = defineTag(IfdId.TYPE_IFD_0, (short) 0x013B);
    public static final int TAG_WHITE_POINT = defineTag(IfdId.TYPE_IFD_0, (short) 0x013E);
    public static final int TAG_PRIMARY_CHROMATICITIES = defineTag(IfdId.TYPE_IFD_0, (short) 0x013F);
    public static final int TAG_Y_CB_CR_COEFFICIENTS = defineTag(IfdId.TYPE_IFD_0, (short) 0x0211);
    public static final int TAG_Y_CB_CR_SUB_SAMPLING = defineTag(IfdId.TYPE_IFD_0, (short) 0x0212);
    public static final int TAG_Y_CB_CR_POSITIONING = defineTag(IfdId.TYPE_IFD_0, (short) 0x0213);
    public static final int TAG_REFERENCE_BLACK_WHITE = defineTag(IfdId.TYPE_IFD_0, (short) 0x0214);
    /**
     * Values is ascii string<br />
     * Shows copyright information
     */
    public static final int TAG_COPYRIGHT = defineTag(IfdId.TYPE_IFD_0, (short) 0x8298);
    public static final int TAG_EXIF_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8769);
    public static final int TAG_GPS_IFD = defineTag(IfdId.TYPE_IFD_0, (short) 0x8825);
    // IFD 1
    public static final int TAG_JPEG_INTERCHANGE_FORMAT = defineTag(IfdId.TYPE_IFD_1, (short) 0x0201);
    public static final int TAG_JPEG_INTERCHANGE_FORMAT_LENGTH = defineTag(IfdId.TYPE_IFD_1, (short) 0x0202);
    /**
     * Value is unsigned double<br />
     * Exposure time (reciprocal of shutter speed). Unit is second
     */
    public static final int TAG_EXPOSURE_TIME = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829A);
    // IFD Exif Tags
    /**
     * Value is unsigned double<br />
     * The actual F-number(F-stop) of lens when the image was taken
     *
     * @see #TAG_APERTURE_VALUE
     */
    public static final int TAG_F_NUMBER = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x829D);
    /**
     * Value is unsigned int.<br />
     * Exposure program that the camera used when image was taken.
     * <ul>
     * <li>'1' means manual control</li>
     * <li>'2' program normal</li>
     * <li>'3' aperture priority</li>
     * <li>'4' shutter priority</li>
     * <li>'5' program creative (slow program)</li>
     * <li>'6' program action(high-speed program)</li>
     * <li>'7' portrait mode</li>
     * <li>'8' landscape mode.</li>
     * </ul>
     */
    public static final int TAG_EXPOSURE_PROGRAM = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8822);
    public static final int TAG_SPECTRAL_SENSITIVITY = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8824);
    /**
     * Value is unsigned int.<br />
     * CCD sensitivity equivalent to Ag-Hr film speedrate.<br />
     * Indicates the ISO Speed and ISO Latitude of the camera or input device as specified in ISO 12232
     */
    public static final int TAG_ISO_SPEED_RATINGS = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8827);
    public static final int TAG_OECF = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8828);
    /**
     * ASCII string (4).<br />
     * The version of this standard supported. Nonexistence of this field is taken to mean nonconformance to the standard (see
     * section 4.2). Conformance to this standard is indicated by recording "0220" as 4-byte ASCII
     */
    public static final int TAG_EXIF_VERSION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9000);
    /**
     * Value is ascii string (20)<br />
     * Date/Time of original image taken. This value should not be modified by user program.
     */
    public static final int TAG_DATE_TIME_ORIGINAL = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9003);
    /**
     * Value is ascii string (20)<br />
     * Date/Time of image digitized. Usually, it contains the same value of DateTimeOriginal(0x9003).
     */
    public static final int TAG_DATE_TIME_DIGITIZED = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9004);
    public static final int TAG_COMPONENTS_CONFIGURATION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9101);
    public static final int TAG_COMPRESSED_BITS_PER_PIXEL = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9102);
    /**
     * Value is signed double.<br />
     * Shutter speed. To convert this value to ordinary 'Shutter Speed'; calculate this value's power of 2, then reciprocal. For
     * example, if value is '4', shutter speed is 1/(2^4)=1/16 second.
     */
    public static final int TAG_SHUTTER_SPEED_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9201);
    /**
     * Value is unsigned double<br />
     * The actual aperture value of lens when the image was taken.<br />
     * To convert this value to ordinary F-number(F-stop), calculate this value's power of root 2 (=1.4142).<br />
     * For example, if value is '5', F-number is 1.4142^5 = F5.6<br />
     * <p/>
     * <pre>
     * FNumber = Math.exp( ApertureValue * Math.log( 2 ) * 0.5 );
     * </pre>
     *
     * @see #TAG_F_NUMBER
     */
    public static final int TAG_APERTURE_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9202);
    /**
     * Value is signed double<br />
     * Brightness of taken subject, unit is EV.
     */
    public static final int TAG_BRIGHTNESS_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9203);
    /**
     * Value is signed double.<br />
     * The exposure bias. The unit is the APEX value. Ordinarily it is given in the range of -99.99 to 99.99
     */
    public static final int TAG_EXPOSURE_BIAS_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9204);
    /**
     * Value is unsigned double.<br />
     * Maximum aperture value of lens.<br />
     * You can convert to F-number by calculating power of root 2 (same process of ApertureValue(0x9202).<br />
     * <p/>
     * <pre>
     * FNumber = Math.exp( MaxApertureValue * Math.log( 2 ) * 0.5 )
     * </pre>
     */
    public static final int TAG_MAX_APERTURE_VALUE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9205);
    /**
     * Value if signed double.<br />
     * Distance to focus point, unit is meter. If value < 0 then focus point is infinite
     */
    public static final int TAG_SUBJECT_DISTANCE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9206);
    /**
     * Value is unsigned int.<br />
     * Exposure metering method:
     * <ul>
     * <li>0 = unknown</li>
     * <li>1 = Average</li>
     * <li>2 = CenterWeightedAverage</li>
     * <li>3 = Spot</li>
     * <li>4 = MultiSpot</li>
     * <li>5 = Pattern</li>
     * <li>6 = Partial</li>
     * <li>Other = reserved</li>
     * <li>255 = other</li>
     * </ul>
     */
    public static final int TAG_METERING_MODE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9207);
    /**
     * Value is unsigned int.<br />
     * Light source, actually this means white balance setting.
     * <ul>
     * <li>0 = means auto</li>
     * <li>1 = Daylight</li>
     * <li>2 = Fluorescent</li>
     * <li>3 = Tungsten (incandescent light)</li>
     * <li>4 = Flash</li>
     * <li>9 = Fine weather</li>
     * <li>10 = Cloudy weather</li>
     * <li>11 = Shade</li>
     * <li>12 = Daylight fluorescent (D 5700 - 7100K)</li>
     * <li>13 = Day white fluorescent (N 4600 - 5400K)</li>
     * <li>14 = Cool white fluorescent (W 3900 - 4500K)</li>
     * <li>15 = White fluorescent (WW 3200 - 3700K)</li>
     * <li>17 = Standard light A</li>
     * <li>18 = Standard light B</li>
     * <li>19 = Standard light C</li>
     * <li>20 = D55</li>
     * <li>21 = D65</li>
     * <li>22 = D75</li>
     * <li>23 = D50</li>
     * <li>24 = ISO studio tungsten</li>
     * <li>255 = other light source</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_LIGHT_SOURCE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9208);
    /**
     * Value is unsigned integer<br />
     * The 8 bits can be extracted and evaluated in this way:<br />
     * <ol>
     * <li>Bit 0 indicates the flash firing status</li>
     * <li>bits 1 and 2 indicate the flash return status</li>
     * <li>bits 3 and 4 indicate the flash mode</li>
     * <li>bit 5 indicates whether the flash function is present</li>
     * <li>and bit 6 indicates "red eye" mode</li>
     * <li>bit 7 unused</li>
     * </ol>
     * <p/>
     * Resulting Flash tag values are:<br />
     * <ul>
     * <li>0000.H = Flash did not fire</li>
     * <li>0001.H = Flash fired</li>
     * <li>0005.H = Strobe return light not detected</li>
     * <li>0007.H = Strobe return light detected</li>
     * <li>0009.H = Flash fired, compulsory flash mode</li>
     * <li>000D.H = Flash fired, compulsory flash mode, return light not detected</li>
     * <li>000F.H = Flash fired, compulsory flash mode, return light detected</li>
     * <li>0010.H = Flash did not fire, compulsory flash mode</li>
     * <li>0018.H = Flash did not fire, auto mode</li>
     * <li>0019.H = Flash fired, auto mode</li>
     * <li>001D.H = Flash fired, auto mode, return light not detected</li>
     * <li>001F.H = Flash fired, auto mode, return light detected</li>
     * <li>0020.H = No flash function</li>
     * <li>0041.H = Flash fired, red-eye reduction mode</li>
     * <li>0045.H = Flash fired, red-eye reduction mode, return light not detected</li>
     * <li>0047.H = Flash fired, red-eye reduction mode, return light detected</li>
     * <li>0049.H = Flash fired, compulsory flash mode, red-eye reduction mode</li>
     * <li>004D.H = Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected</li>
     * <li>004F.H = Flash fired, compulsory flash mode, red-eye reduction mode, return light detected</li>
     * <li>0059.H = Flash fired, auto mode, red-eye reduction mode</li>
     * <li>005D.H = Flash fired, auto mode, return light not detected, red-eye reduction mode</li>
     * <li>005F.H = Flash fired, auto mode, return light detected, red-eye reduction mode</li>
     * <li>Other = reserved</li>
     * </ul>
     *
     * @see <a href="http://www.exif.org/Exif2-2.PDF">http://www.exif.org/Exif2-2.PDF</a>
     */
    public static final int TAG_FLASH = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9209);
    /**
     * Value is unsigned double<br />
     * Focal length of lens used to take image. Unit is millimeter.
     */
    public static final int TAG_FOCAL_LENGTH = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x920A);
    public static final int TAG_SUBJECT_AREA = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9214);
    public static final int TAG_MAKER_NOTE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x927C);
    public static final int TAG_USER_COMMENT = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9286);
    public static final int TAG_SUB_SEC_TIME = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9290);
    public static final int TAG_SUB_SEC_TIME_ORIGINAL = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9291);
    public static final int TAG_SUB_SEC_TIME_DIGITIZED = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x9292);
    public static final int TAG_FLASHPIX_VERSION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA000);
    /**
     * Value is int.<br />
     * Normally sRGB (=1) is used to define the color space based on the PC monitor conditions and environment. If a color space
     * other than sRGB is used, Uncalibrated (=FFFF.H) is set. Image data recorded as Uncalibrated can be treated as sRGB when it is
     * converted to Flashpix. On sRGB see Annex E.
     * <ul>
     * <li>'1' = sRGB</li>
     * <li>'FFFF' = Uncalibrated</li>
     * <li>'other' = Reserved</li>
     * </ul>
     */
    public static final int TAG_COLOR_SPACE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA001);
    /**
     * Value is unsigned int.<br />
     * Specific to compressed data; the valid width of the meaningful image. When a compressed file is recorded, the valid width of
     * the meaningful image shall be recorded in this tag, whether or not there is padding data or a restart marker. This tag should
     * not exist in an uncompressed file.
     */
    public static final int TAG_PIXEL_X_DIMENSION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA002);
    /**
     * @see #TAG_PIXEL_X_DIMENSION
     */
    public static final int TAG_PIXEL_Y_DIMENSION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA003);
    public static final int TAG_RELATED_SOUND_FILE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA004);
    public static final int TAG_INTEROPERABILITY_IFD = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA005);
    public static final int TAG_FLASH_ENERGY = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20B);
    public static final int TAG_SPATIAL_FREQUENCY_RESPONSE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20C);
    /**
     * Value is unsigned double.<br />
     * Indicates the number of pixels in the image width (X) direction per FocalPlaneResolutionUnit on the camera focal plane. CCD's
     * pixel density
     *
     * @see #TAG_FOCAL_PLANE_RESOLUTION_UNIT
     */
    public static final int TAG_FOCAL_PLANE_X_RESOLUTION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20E);
    /**
     * @see #TAG_FOCAL_PLANE_X_RESOLUTION
     */
    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA20F);
    /**
     * Value is unsigned int.<br />
     * Unit of FocalPlaneXResoluton/FocalPlaneYResolution.
     * <ul>
     * <li>'1' means no-unit</li>
     * <li>'2' inch</li>
     * <li>'3' centimeter</li>
     * <li>'4' millimeter</li>
     * <li>'5' micrometer</li>
     * </ul>
     * <p/>
     * This tag can be used to calculate the CCD Width:
     * <p/>
     * <pre>
     * CCDWidth = ( PixelXDimension * FocalPlaneResolutionUnit / FocalPlaneXResolution )
     * </pre>
     */
    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA210);
    public static final int TAG_SUBJECT_LOCATION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA214);
    public static final int TAG_EXPOSURE_INDEX = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA215);
    /**
     * Value is unsigned int.<br />
     * Indicates the image sensor type on the camera or input device. The values are as follows:
     * <ul>
     * <li>1 = Not defined</li>
     * <li>2 = One-chip color area sensor</li>
     * <li>3 = Two-chip color area sensor JEITA CP-3451 - 41</li>
     * <li>4 = Three-chip color area sensor</li>
     * <li>5 = Color sequential area sensor</li>
     * <li>7 = Trilinear sensor</li>
     * <li>8 = Color sequential linear sensor</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_SENSING_METHOD = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA217);
    public static final int TAG_FILE_SOURCE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA300);
    public static final int TAG_SCENE_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA301);
    public static final int TAG_CFA_PATTERN = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA302);
    public static final int TAG_CUSTOM_RENDERED = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA401);
    /**
     * Value is int.<br />
     * This tag indicates the exposure mode set when the image was shot. In auto-bracketing mode, the camera shoots a series of
     * frames of the same scene at different exposure settings.
     * <ul>
     * <li>0 = Auto exposure</li>
     * <li>1 = Manual exposure</li>
     * <li>2 = Auto bracket</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_EXPOSURE_MODE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA402);
    public static final int TAG_WHITE_BALANCE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA403);
    /**
     * Value is double.<br />
     * This tag indicates the digital zoom ratio when the image was shot. If the numerator of the recorded value is 0, this indicates
     * that digital zoom was not used
     */
    public static final int TAG_DIGITAL_ZOOM_RATIO = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA404);
    /**
     * Value is unsigned int.<br />
     * This tag indicates the equivalent focal length assuming a 35mm film camera, in mm.<br />
     * Exif 2.2 tag, usually not present, it can be calculated by:
     * <p/>
     * <pre>
     * CCDWidth = ( PixelXDimension * FocalplaneUnits / FocalplaneXRes );
     * FocalLengthIn35mmFilm = ( FocalLength / CCDWidth * 36 + 0.5 );
     * </pre>
     */
    public static final int TAG_FOCAL_LENGTH_IN_35_MM_FILE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA405);
    /**
     * Value is int.<br />
     * This tag indicates the type of scene that was shot. It can also be used to record the mode in which the image was shot. Note
     * that this differs from the scene type (SceneType) tag.
     * <ul>
     * <li>0 = Standard</li>
     * <li>1 = Landscape</li>
     * <li>2 = Portrait</li>
     * <li>3 = Night scene</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_SCENE_CAPTURE_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA406);
    /**
     * Value is int.<br />
     * This tag indicates the degree of overall image gain adjustment.
     * <ul>
     * <li>0 = None</li>
     * <li>1 = Low gain up</li>
     * <li>2 = High gain up</li>
     * <li>3 = Low gain down</li>
     * <li>4 = High gain down</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_GAIN_CONTROL = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA407);
    /**
     * Value is int.<br />
     * This tag indicates the direction of contrast processing applied by the camera when the image was shot.
     * <ul>
     * <li>0 = Normal</li>
     * <li>1 = Soft</li>
     * <li>2 = Hard</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_CONTRAST = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA408);
    /**
     * Value is int.<br />
     * This tag indicates the direction of saturation processing applied by the camera when the image was shot.
     * <ul>
     * <li>0 = Normal</li>
     * <li>1 = Low saturation</li>
     * <li>2 = High saturation</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_SATURATION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA409);
    /**
     * Value is int.<br />
     * This tag indicates the direction of sharpness processing applied by the camera when the image was shot
     * <ul>
     * <li>0 = Normal</li>
     * <li>1 = Soft</li>
     * <li>2 = Hard</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_SHARPNESS = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40A);
    public static final int TAG_DEVICE_SETTING_DESCRIPTION = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40B);
    /**
     * Value is int.<br />
     * This tag indicates the distance to the subject.
     * <ul>
     * <li>0 = unknown</li>
     * <li>1 = Macro</li>
     * <li>2 = Close view</li>
     * <li>3 = Distant view</li>
     * <li>Other = reserved</li>
     * </ul>
     */
    public static final int TAG_SUBJECT_DISTANCE_RANGE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA40C);
    /**
     * {@link ExifTag#TYPE_ASCII}
     */
    public static final int TAG_IMAGE_UNIQUE_ID = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA420);
    /**
     * Lens Specifications. The value it's a 4 rational containing:
     * <ol>
     * <li>Minimum focal length (in mm)</li>
     * <li>Maximum focal length (in mm)</li>
     * <li>Minimum F Number in the minimum focal length</li>
     * <li>Maximum F Number in the maximum focal length</li>
     * </ol>
     * <p>
     * {@link ExifTag#TYPE_RATIONAL}
     *
     * @see it.sephiroth.android.library.exif2.ExifUtil#processLensSpecifications(Rational[])
     * @since EXIF 2.3
     */
    public static final int TAG_LENS_SPECS = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA432);
    /**
     * Lens maker
     * {@link ExifTag#TYPE_ASCII}
     *
     * @since EXIF 2.3
     */
    public static final int TAG_LENS_MAKE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA433);
    /**
     * Lens model name and number
     * {@link ExifTag#TYPE_ASCII}
     *
     * @since EXIF 2.3
     */
    public static final int TAG_LENS_MODEL = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0xA434);
    /**
     * The SensitivityType tag indicates which one of the parameters of ISO12232 is the
     * PhotographicSensitivity tag. Although it is an optional tag, it should be recorded
     * when a PhotographicSensitivity tag is recorded.
     * Value = 4, 5, 6, or 7 may be used in case that the values of plural
     * parameters are the same.<br/>
     * Values:
     * <ul>
     * <li>0: Unknown</li>
     * <li>1: Standardoutputsensitivity(SOS)</li>
     * <li>2: Recommended exposure index (REI)</li>
     * <li>3: ISOspeed</li>
     * <li>4: Standard output sensitivity (SOS) and recommended exposure index (REI)</li>
     * <li>5: Standardoutputsensitivity(SOS)andISOspeed</li>
     * <li>6: Recommendedexposureindex(REI)andISOspeed</li>
     * <li>7: Standard output sensitivity (SOS) and recommended exposure index (REI) and ISO speed</li>
     * <li>Other: Reserved</li>
     * </ul>
     * <p>
     * {@link ExifTag#TYPE_UNSIGNED_SHORT}
     *
     * @see it.sephiroth.android.library.exif2.ExifInterface.SensitivityType
     * @since EXIF 2.3
     */
    public static final int TAG_SENSITIVITY_TYPE = defineTag(IfdId.TYPE_IFD_EXIF, (short) 0x8830);
    // IFD GPS tags
    public static final int TAG_GPS_VERSION_ID = defineTag(IfdId.TYPE_IFD_GPS, (short) 0);
    /**
     * Value is string(1)<br />
     * Indicates whether the latitude is north or south latitude. The ASCII value 'N' indicates north latitude, and 'S' is south latitude.
     */
    public static final int TAG_GPS_LATITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 1);
    /**
     * Value is string.<br />
     * Indicates the latitude. The latitude is expressed as three RATIONAL values giving the degrees, minutes, and
     * seconds, respectively. If latitude is expressed as degrees, minutes and seconds, a typical format would be
     * dd/1,mm/1,ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two
     * decimal places, the format would be dd/1,mmmm/100,0/1.
     */
    public static final int TAG_GPS_LATITUDE = defineTag(IfdId.TYPE_IFD_GPS, (short) 2);
    /**
     * Value is string(1)<br />
     * Indicates whether the longitude is east or west longitude. ASCII 'E' indicates east longitude, and 'W' is west longitude.
     */
    public static final int TAG_GPS_LONGITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 3);
    /**
     * Value is string.<br />
     * Indicates the longitude. The longitude is expressed as three RATIONAL values giving the degrees, minutes, and
     * seconds, respectively. If longitude is expressed as degrees, minutes and seconds, a typical format would be
     * ddd/1,mm/1,ss/1. When degrees and minutes are used and, for example, fractions of minutes are given up to two
     * decimal places, the format would be ddd/1,mmmm/100,0/1.
     */
    public static final int TAG_GPS_LONGITUDE = defineTag(IfdId.TYPE_IFD_GPS, (short) 4);
    /**
     * Value is byte<br />
     * Indicates the altitude used as the reference altitude. If the reference is sea level and the altitude is above sea level,
     * 0 is given. If the altitude is below sea level, a value of 1 is given and the altitude is indicated as an absolute value in
     * the GPSAltitude tag. The reference unit is meters. Note that this tag is BYTE type, unlike other reference tags
     */
    public static final int TAG_GPS_ALTITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 5);
    /**
     * Value is string.<br />
     * Indicates the altitude based on the reference in GPSAltitudeRef. Altitude is expressed as one RATIONAL value. The reference unit is meters.
     */
    public static final int TAG_GPS_ALTITUDE = defineTag(IfdId.TYPE_IFD_GPS, (short) 6);
    public static final int TAG_GPS_TIME_STAMP = defineTag(IfdId.TYPE_IFD_GPS, (short) 7);
    public static final int TAG_GPS_SATTELLITES = defineTag(IfdId.TYPE_IFD_GPS, (short) 8);
    public static final int TAG_GPS_STATUS = defineTag(IfdId.TYPE_IFD_GPS, (short) 9);
    public static final int TAG_GPS_MEASURE_MODE = defineTag(IfdId.TYPE_IFD_GPS, (short) 10);
    public static final int TAG_GPS_DOP = defineTag(IfdId.TYPE_IFD_GPS, (short) 11);
    /**
     * Value is string(1).<br />
     * Indicates the unit used to express the GPS receiver speed of movement. 'K' 'M' and 'N' represents kilometers per  hour, miles per hour, and knots.
     */
    public static final int TAG_GPS_SPEED_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 12);
    /**
     * Value is string.<br />
     * Indicates the speed of GPS receiver movement
     */
    public static final int TAG_GPS_SPEED = defineTag(IfdId.TYPE_IFD_GPS, (short) 13);
    public static final int TAG_GPS_TRACK_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 14);
    public static final int TAG_GPS_TRACK = defineTag(IfdId.TYPE_IFD_GPS, (short) 15);
    public static final int TAG_GPS_IMG_DIRECTION_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 16);
    public static final int TAG_GPS_IMG_DIRECTION = defineTag(IfdId.TYPE_IFD_GPS, (short) 17);
    public static final int TAG_GPS_MAP_DATUM = defineTag(IfdId.TYPE_IFD_GPS, (short) 18);
    public static final int TAG_GPS_DEST_LATITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 19);
    public static final int TAG_GPS_DEST_LATITUDE = defineTag(IfdId.TYPE_IFD_GPS, (short) 20);
    public static final int TAG_GPS_DEST_LONGITUDE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 21);
    public static final int TAG_GPS_DEST_LONGITUDE = defineTag(IfdId.TYPE_IFD_GPS, (short) 22);
    public static final int TAG_GPS_DEST_BEARING_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 23);
    public static final int TAG_GPS_DEST_BEARING = defineTag(IfdId.TYPE_IFD_GPS, (short) 24);
    public static final int TAG_GPS_DEST_DISTANCE_REF = defineTag(IfdId.TYPE_IFD_GPS, (short) 25);
    public static final int TAG_GPS_DEST_DISTANCE = defineTag(IfdId.TYPE_IFD_GPS, (short) 26);
    public static final int TAG_GPS_PROCESSING_METHOD = defineTag(IfdId.TYPE_IFD_GPS, (short) 27);
    public static final int TAG_GPS_AREA_INFORMATION = defineTag(IfdId.TYPE_IFD_GPS, (short) 28);
    public static final int TAG_GPS_DATE_STAMP = defineTag(IfdId.TYPE_IFD_GPS, (short) 29);
    public static final int TAG_GPS_DIFFERENTIAL = defineTag(IfdId.TYPE_IFD_GPS, (short) 30);
    // IFD Interoperability tags
    public static final int TAG_INTEROPERABILITY_INDEX = defineTag(IfdId.TYPE_IFD_INTEROPERABILITY, (short) 1);
    public static final ByteOrder DEFAULT_BYTE_ORDER = ByteOrder.BIG_ENDIAN;
    private static final String TAG = "ExifInterface";
    private static final String NULL_ARGUMENT_STRING = "Argument is null";
    private static final String GPS_DATE_FORMAT_STR = "yyyy:MM:dd";
    private static final DateFormat mGPSDateStampFormat = new SimpleDateFormat(GPS_DATE_FORMAT_STR);
    private static final String DATETIME_FORMAT_STR = "yyyy:MM:dd kk:mm:ss";
    private static final DateFormat mDateTimeStampFormat = new SimpleDateFormat(DATETIME_FORMAT_STR);
    /**
     * Tags that contain offset markers. These are included in the banned
     * defines.
     */
    private static HashSet<Short> sOffsetTags = new HashSet<Short>();
    /**
     * Tags with definitions that cannot be overridden (banned defines).
     */
    protected static HashSet<Short> sBannedDefines = new HashSet<Short>(sOffsetTags);

    static {
        sOffsetTags.add(getTrueTagKey(TAG_GPS_IFD));
        sOffsetTags.add(getTrueTagKey(TAG_EXIF_IFD));
        sOffsetTags.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT));
        sOffsetTags.add(getTrueTagKey(TAG_INTEROPERABILITY_IFD));
        sOffsetTags.add(getTrueTagKey(TAG_STRIP_OFFSETS));
    }

    static {
        sBannedDefines.add(getTrueTagKey(TAG_NULL));
        sBannedDefines.add(getTrueTagKey(TAG_JPEG_INTERCHANGE_FORMAT_LENGTH));
        sBannedDefines.add(getTrueTagKey(TAG_STRIP_BYTE_COUNTS));
    }

    private final Calendar mGPSTimeStampCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    private ExifData mData = new ExifData(DEFAULT_BYTE_ORDER);
    private SparseIntArray mTagInfo = null;

    public ExifInterface() {
        mGPSDateStampFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
    }

    /**
     * Returns true if tag TID is one of the following: {@link #TAG_EXIF_IFD},
     * {@link #TAG_GPS_IFD}, {@link #TAG_JPEG_INTERCHANGE_FORMAT},
     * {@link #TAG_STRIP_OFFSETS}, {@link #TAG_INTEROPERABILITY_IFD}
     * <p/>
     * Note: defining tags with these TID's is disallowed.
     *
     * @param tag a tag's TID (can be obtained from a defined tag constant with
     *            {@link #getTrueTagKey}).
     * @return true if the TID is that of an offset tag.
     */
    protected static boolean isOffsetTag(short tag) {
        return sOffsetTags.contains(tag);
    }

    /**
     * Returns the Orientation ExifTag value for a given number of degrees.
     *
     * @param degrees the amount an image is rotated in degrees.
     */
    public static short getOrientationValueForRotation(int degrees) {
        degrees %= 360;
        if (degrees < 0) {
            degrees += 360;
        }
        if (degrees < 90) {
            return Orientation.TOP_LEFT; // 0 degrees
        } else if (degrees < 180) {
            return Orientation.RIGHT_TOP; // 90 degrees cw
        } else if (degrees < 270) {
            return Orientation.BOTTOM_LEFT; // 180 degrees
        } else {
            return Orientation.RIGHT_BOTTOM; // 270 degrees cw
        }
    }

    /**
     * Returns the rotation degrees corresponding to an ExifTag Orientation
     * value.
     *
     * @param orientation the ExifTag Orientation value.
     */
    @SuppressWarnings("unused")
    public static int getRotationForOrientationValue(short orientation) {
        switch (orientation) {
            case Orientation.TOP_LEFT:
                return 0;
            case Orientation.RIGHT_TOP:
                return 90;
            case Orientation.BOTTOM_LEFT:
                return 180;
            case Orientation.RIGHT_BOTTOM:
                return 270;
            default:
                return 0;
        }
    }

    /**
     * Gets the double representation of the GPS latitude or longitude
     * coordinate.
     *
     * @param coordinate an array of 3 Rationals representing the degrees,
     *                   minutes, and seconds of the GPS location as defined in the
     *                   exif specification.
     * @param reference  a GPS reference reperesented by a String containing "N",
     *                   "S", "E", or "W".
     * @return the GPS coordinate represented as degrees + minutes/60 +
     * seconds/3600
     */
    public static double convertLatOrLongToDouble(Rational[] coordinate, String reference) {
        try {
            double degrees = coordinate[0].toDouble();
            double minutes = coordinate[1].toDouble();
            double seconds = coordinate[2].toDouble();
            double result = degrees + minutes / 60.0 + seconds / 3600.0;
            if ((reference.startsWith("S") || reference.startsWith("W"))) {
                return -result;
            }
            return result;
        } catch (ArrayIndexOutOfBoundsException e) {
            throw new IllegalArgumentException();
        }
    }

    protected static int[] getAllowedIfdsFromInfo(int info) {
        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
        int[] ifds = IfdData.getIfds();
        ArrayList<Integer> l = new ArrayList<Integer>();
        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
            int flag = (ifdFlags >> i) & 1;
            if (flag == 1) {
                l.add(ifds[i]);
            }
        }
        if (l.size() <= 0) {
            return null;
        }
        int[] ret = new int[l.size()];
        int j = 0;
        for (int i : l) {
            ret[j++] = i;
        }
        return ret;
    }

    protected static void closeSilently(Closeable c) {
        if (c != null) {
            try {
                c.close();
            } catch (Throwable e) {
                // ignored
            }
        }
    }

    private static int writeExif_internal(final InputStream input, final OutputStream output, ExifData exifData) throws IOException {
        // Log.i( TAG, "writeExif_internal" );

        // 1. read the output file first
        ExifInterface src_exif = new ExifInterface();
        src_exif.readExif(input, 0);

        // 4. Create the destination outputstream
        // 5. write headers
        output.write(0xFF);
        output.write(JpegHeader.TAG_SOI);

        final List<ExifParser.Section> sections = src_exif.mData.getSections();

        // 6. write all the sections from the srcFilename
        if (sections.get(0).type != JpegHeader.TAG_M_JFIF) {
            Log.w(TAG, "first section is not a JFIF or EXIF tag");
            output.write(JpegHeader.JFIF_HEADER);
        }

        // 6.1 write the *new* EXIF tag
        ExifOutputStream eo = new ExifOutputStream(src_exif);
        eo.setExifData(exifData);
        eo.writeExifData(output);

        // 6.2 write all the sections except for the SOS ( start of scan )
        for (int a = 0; a < sections.size() - 1; a++) {
            ExifParser.Section current = sections.get(a);
            // Log.v( TAG, "writing section.. " + String.format( "0x%2X", current.type ) );
            output.write(0xFF);
            output.write(current.type);
            output.write(current.data);
        }

        // 6.3 write the last SOS marker
        ExifParser.Section current = sections.get(sections.size() - 1);
        // Log.v( TAG, "writing last section.. " + String.format( "0x%2X", current.type ) );
        output.write(0xFF);
        output.write(current.type);
        output.write(current.data);

        // return the position where the input stream should be copied
        return src_exif.mData.mUncompressedDataPosition;
    }

    /**
     * Returns the default IFD for a tag constant.
     */
    public static int getTrueIfd(int tag) {
        return tag >>> 16;
    }

    /**
     * Returns the TID for a tag constant.
     */
    public static short getTrueTagKey(int tag) {
        // Truncate
        return (short) tag;
    }

    protected static int getFlagsFromAllowedIfds(int[] allowedIfds) {
        if (allowedIfds == null || allowedIfds.length == 0) {
            return 0;
        }
        int flags = 0;
        int[] ifds = IfdData.getIfds();
        for (int i = 0; i < IfdId.TYPE_IFD_COUNT; i++) {
            for (int j : allowedIfds) {
                if (ifds[i] == j) {
                    flags |= 1 << i;
                    break;
                }
            }
        }
        return flags;
    }

    protected static int getComponentCountFromInfo(int info) {
        return info & 0x0ffff;
    }

    protected static short getTypeFromInfo(int info) {
        return (short) ((info >> 16) & 0x0ff);
    }

    /**
     * Returns the constant representing a tag with a given TID and default IFD.
     */
    public static int defineTag(int ifdId, short tagId) {
        return (tagId & 0x0000ffff) | (ifdId << 16);
    }

    private static String convertRationalLatLonToString(Rational[] coord, String ref) {
        try {

            double degrees = coord[0].toDouble();
            double minutes = coord[1].toDouble();
            double seconds = coord[2].toDouble();
            ref = ref.substring(0, 1);

            return String.format("%1$.0f° %2$.0f' %3$.0f\" %4$s", degrees, minutes, seconds, ref.toUpperCase(Locale.getDefault()));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        } catch (ArrayIndexOutOfBoundsException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Given an exif date time, like {@link #TAG_DATE_TIME} or {@link #TAG_DATE_TIME_DIGITIZED}
     * returns a java Date object
     *
     * @param dateTimeString one of the value of {@link #TAG_DATE_TIME} or {@link #TAG_DATE_TIME_DIGITIZED}
     * @param timeZone       the target timezone
     * @return the parsed date
     */
    public static Date getDateTime(String dateTimeString, TimeZone timeZone) {
        if (dateTimeString == null) return null;

        DateFormat formatter = new SimpleDateFormat(DATETIME_FORMAT_STR);
        formatter.setTimeZone(timeZone);

        try {
            return formatter.parse(dateTimeString);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return null;
    }

    protected static boolean isIfdAllowed(int info, int ifd) {
        int[] ifds = IfdData.getIfds();
        int ifdFlags = getAllowedIfdFlagsFromInfo(info);
        for (int i = 0; i < ifds.length; i++) {
            if (ifd == ifds[i] && ((ifdFlags >> i) & 1) == 1) {
                return true;
            }
        }
        return false;
    }

    protected static int getAllowedIfdFlagsFromInfo(int info) {
        return info >>> 24;
    }

    private static Rational[] toExifLatLong(double value) {
        // convert to the format dd/1 mm/1 ssss/100
        value = Math.abs(value);
        int degrees = (int) value;
        value = (value - degrees) * 60;
        int minutes = (int) value;
        value = (value - minutes) * 6000;
        int seconds = (int) value;
        return new Rational[]{new Rational(degrees, 1), new Rational(minutes, 1), new Rational(seconds, 100)};
    }

    @SuppressWarnings("unused")
    public static byte[] toBitArray(short value) {
        byte[] result = new byte[16];
        for (int i = 0; i < 16; i++) {
            result[15 - i] = (byte) ((value >> i) & 1);
        }
        return result;
    }

    /**
     * Given the value from {@link #TAG_FOCAL_PLANE_RESOLUTION_UNIT} or {@link #TAG_RESOLUTION_UNIT}
     * this method will return the corresponding value in millimeters
     *
     * @param resolution {@link #TAG_FOCAL_PLANE_RESOLUTION_UNIT} or {@link #TAG_RESOLUTION_UNIT}
     * @return resolution in millimeters
     */
    @SuppressWarnings("unused")
    public double getResolutionUnit(int resolution) {
        switch (resolution) {
            case 1:
            case ResolutionUnit.INCHES:
                return 25.4;

            case ResolutionUnit.CENTIMETERS:
                return 10;

            case ResolutionUnit.MILLIMETERS:
                return 1;

            case ResolutionUnit.MICROMETERS:
                return .001;

            default:
                return 25.4;
        }
    }

    /**
     * Reads the exif tags from a file, clearing this ExifInterface object's
     * existing exif tags.
     *
     * @param inFileName a string representing the filepath to jpeg file.
     * @param options    bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
     * @throws java.io.IOException
     * @see #readExif(java.io.InputStream, int)
     */
    @SuppressWarnings("unused")
    public void readExif(String inFileName, int options) throws IOException {
        if (inFileName == null) {
            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
        }
        InputStream is = null;
        try {
            is = new BufferedInputStream(new FileInputStream(inFileName));
            readExif(is, options);
        } catch (IOException e) {
            closeSilently(is);
            throw e;
        }
        is.close();
    }

    /**
     * Reads the exif tags from an InputStream, clearing this ExifInterface
     * object's existing exif tags.
     * <pre>
     *     ExifInterface exif = new ExifInterface();
     *     exif.readExif( stream, Options.OPTION_IFD_0 | Options.OPTION_IFD_1 | Options.OPTION_IFD_EXIF );
     *     ...
     *     // to request all the options use the OPTION_ALL bit mask
     *     exif.readExif( stream, Options.OPTION_ALL );
     * </pre>
     *
     * @param inStream an InputStream containing a jpeg compressed image.
     * @param options  bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
     * @throws java.io.IOException
     */
    @SuppressWarnings("unused")
    public void readExif(InputStream inStream, int options) throws IOException {
        if (inStream == null) {
            throw new IllegalArgumentException(NULL_ARGUMENT_STRING);
        }
        ExifData d;
        try {
            d = new ExifReader(this).read(inStream, options);
        } catch (ExifInvalidFormatException e) {
            throw new IOException("Invalid exif format : " + e);
        }
        mData = d;
    }

    /**
     * Sets the exif tags, clearing this ExifInterface object's existing exif
     * tags.
     *
     * @param tags a collection of exif tags to set.
     */
    public void setExif(Collection<ExifTag> tags) {
        clearExif();
        setTags(tags);
    }

    /**
     * Clears this ExifInterface object's existing exif tags.
     */
    public void clearExif() {
        mData = new ExifData(DEFAULT_BYTE_ORDER);
    }

    /**
     * Puts a collection of ExifTags into this ExifInterface objects's tags. Any
     * previous ExifTags with the same TID and IFDs will be removed.
     *
     * @param tags a Collection of ExifTags.
     * @see #setTag
     */
    public void setTags(Collection<ExifTag> tags) {
        if (null == tags) return;
        for (ExifTag t : tags) {
            setTag(t);
        }
    }

    /**
     * Puts an ExifTag into this ExifInterface object's tags, removing a
     * previous ExifTag with the same TID and IFD. The IFD it is put into will
     * be the one the tag was created with in {@link #buildTag}.
     *
     * @param tag an ExifTag to put into this ExifInterface's tags.
     * @return the previous ExifTag with the same TID and IFD or null if none
     * exists.
     */
    public ExifTag setTag(ExifTag tag) {
        return mData.addTag(tag);
    }

    @SuppressWarnings("unused")
    public void writeExif(final String dstFilename) throws IOException {
        Log.i(TAG, "writeExif: " + dstFilename);

        // create a backup file
        File dst_file = new File(dstFilename);
        File bak_file = new File(dstFilename + ".t");

        // try to delete old copy of backup
        // Log.d( TAG, "delete old backup file" );
        bak_file.delete();

        // rename dst file into backup file
        // Log.d( TAG, "rename dst into bak" )
        // if( ! dst_file.renameTo( bak_file ) ) return;

        try {
            // Log.d( TAG, "try to write into dst" );
            // writeExif( bak_file.getAbsolutePath(), dst_file.getAbsolutePath() );

            // Trying to write into bak_file using dst_file as source
            writeExif(dst_file.getAbsolutePath(), bak_file.getAbsolutePath());

            // Now switch bak into dst
            // Log.d( TAG, "rename the bak into dst" );
            bak_file.renameTo(dst_file);
        } catch (IOException e) {
            throw e;
        } finally {
            // deleting backup file
            bak_file.delete();
        }
    }

    @SuppressWarnings("unused")
    public void writeExif(final String srcFilename, final String dstFilename) throws IOException {
        Log.i(TAG, "writeExif: " + dstFilename);

        // src and dst cannot be the same
        if (srcFilename.equals(dstFilename)) return;

        // srcFilename is used *ONLY* to read the image uncompressed data
        // exif tags are not used here

        // 3. rename dst file into backup file
        FileInputStream input = new FileInputStream(srcFilename);
        FileOutputStream output = new FileOutputStream(dstFilename);

        int position = writeExif_internal(input, output, mData);

        // 7. write the rest of the image..
        FileChannel in_channel = input.getChannel();
        FileChannel out_channel = output.getChannel();
        in_channel.transferTo(position, in_channel.size() - position, out_channel);
        output.flush();
    }

    public void writeExif(final InputStream input, final String dstFilename) throws IOException {
        Log.i(TAG, "writeExif: " + dstFilename);

        // inpur is used *ONLY* to read the image uncompressed data
        // exif tags are not used here

        FileOutputStream output = new FileOutputStream(dstFilename);
        writeExif_internal(input, output, mData);

        output.flush();
        output.close();
    }

    @SuppressWarnings("unused")
    public void writeExif(final Bitmap input, final String dstFilename, int quality) throws IOException {
        Log.i(TAG, "writeExif: " + dstFilename);

        // inpur is used *ONLY* to read the image uncompressed data
        // exif tags are not used here

        ByteArrayOutputStream out = new ByteArrayOutputStream();
        input.compress(Bitmap.CompressFormat.JPEG, quality, out);

        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        out.close();

        writeExif(in, dstFilename);
    }

    /**
     * Get the exif tags in this ExifInterface object or null if none exist.
     *
     * @return a List of {@link ExifTag}s.
     */
    public List<ExifTag> getAllTags() {
        return mData.getAllTags();
    }

    /**
     * Reads the exif tags from a byte array, clearing this ExifInterface
     * object's existing exif tags.
     *
     * @param jpeg    a byte array containing a jpeg compressed image.
     * @param options bit flag which defines which type of tags to process, see {@link it.sephiroth.android.library.exif2.ExifInterface.Options}
     * @throws java.io.IOException
     * @see #readExif(java.io.InputStream, int)
     */
    @SuppressWarnings("unused")
    public void readExif(byte[] jpeg, int options) throws IOException {
        readExif(new ByteArrayInputStream(jpeg), options);
    }

    /**
     * Returns a list of ExifTags that share a TID (which can be obtained by
     * calling {@link #getTrueTagKey} on a defined tag constant) or null if none
     * exist.
     *
     * @param tagId a TID as defined in the exif standard (or with
     *              {@link #defineTag}).
     * @return a List of {@link ExifTag}s.
     */
    @SuppressWarnings("unused")
    public List<ExifTag> getTagsForTagId(short tagId) {
        return mData.getAllTagsForTagId(tagId);
    }

    /**
     * Returns a list of ExifTags that share an IFD (which can be obtained by
     * calling {@link #getTrueIfd(int)} on a defined tag constant) or null if none
     * exist.
     *
     * @param ifdId an IFD as defined in the exif standard (or with
     *              {@link #defineTag}).
     * @return a List of {@link ExifTag}s.
     */
    @SuppressWarnings("unused")
    public List<ExifTag> getTagsForIfdId(int ifdId) {
        return mData.getAllTagsForIfd(ifdId);
    }

    /**
     * Returns the ExifTag in that tag's default IFD for a defined tag constant
     * or null if none exists.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return an {@link ExifTag} or null if none exists.
     */
    public ExifTag getTag(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTag(tagId, ifdId);
    }

    /**
     * Gets the default IFD for a tag.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return the default IFD for a tag definition or {@link #IFD_NULL} if no
     * definition exists.
     */
    public int getDefinedTagDefaultIfd(int tagId) {
        int info = getTagInfo().get(tagId);
        if (info == DEFINITION_NULL) {
            return IFD_NULL;
        }
        return getTrueIfd(tagId);
    }

    /**
     * Gets an ExifTag for an IFD other than the tag's default.
     *
     * @see #getTag
     */
    public ExifTag getTag(int tagId, int ifdId) {
        if (!ExifTag.isValidIfd(ifdId)) {
            return null;
        }
        return mData.getTag(getTrueTagKey(tagId), ifdId);
    }

    protected SparseIntArray getTagInfo() {
        if (mTagInfo == null) {
            mTagInfo = new SparseIntArray();
            initTagInfo();
        }
        return mTagInfo;
    }

    private void initTagInfo() {
        /**
         * We put tag information in a 4-bytes integer. The first byte a bitmask
         * representing the allowed IFDs of the tag, the second byte is the data
         * type, and the last two byte are a short value indicating the default
         * component count of this tag.
         */
        // IFD0 tags
        int[] ifdAllowedIfds = {IfdId.TYPE_IFD_0, IfdId.TYPE_IFD_1};
        int ifdFlags = getFlagsFromAllowedIfds(ifdAllowedIfds) << 24;
        mTagInfo.put(ExifInterface.TAG_MAKE, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_IMAGE_WIDTH, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_IMAGE_LENGTH, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_BITS_PER_SAMPLE, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3);
        mTagInfo.put(ExifInterface.TAG_COMPRESSION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_PHOTOMETRIC_INTERPRETATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_ORIENTATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SAMPLES_PER_PIXEL, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_PLANAR_CONFIGURATION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_SUB_SAMPLING, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_POSITIONING, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_X_RESOLUTION, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_Y_RESOLUTION, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_RESOLUTION_UNIT, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_STRIP_OFFSETS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
        mTagInfo.put(ExifInterface.TAG_ROWS_PER_STRIP, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_STRIP_BYTE_COUNTS, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16);
        mTagInfo.put(ExifInterface.TAG_TRANSFER_FUNCTION, ifdFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 3 * 256);
        mTagInfo.put(ExifInterface.TAG_WHITE_POINT, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_PRIMARY_CHROMATICITIES, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
        mTagInfo.put(ExifInterface.TAG_Y_CB_CR_COEFFICIENTS, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
        mTagInfo.put(ExifInterface.TAG_REFERENCE_BLACK_WHITE, ifdFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 6);
        mTagInfo.put(ExifInterface.TAG_DATE_TIME, ifdFlags | ExifTag.TYPE_ASCII << 16 | 20);
        mTagInfo.put(ExifInterface.TAG_IMAGE_DESCRIPTION, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_MODEL, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_SOFTWARE, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_ARTIST, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_COPYRIGHT, ifdFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_EXIF_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_IFD, ifdFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        // IFD1 tags
        int[] ifd1AllowedIfds = {IfdId.TYPE_IFD_1};
        int ifdFlags1 = getFlagsFromAllowedIfds(ifd1AllowedIfds) << 24;
        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT, ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_JPEG_INTERCHANGE_FORMAT_LENGTH, ifdFlags1 | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        // Exif tags
        int[] exifAllowedIfds = {IfdId.TYPE_IFD_EXIF};
        int exifFlags = getFlagsFromAllowedIfds(exifAllowedIfds) << 24;
        mTagInfo.put(ExifInterface.TAG_EXIF_VERSION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
        mTagInfo.put(ExifInterface.TAG_FLASHPIX_VERSION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
        mTagInfo.put(ExifInterface.TAG_COLOR_SPACE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_COMPONENTS_CONFIGURATION, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
        mTagInfo.put(ExifInterface.TAG_COMPRESSED_BITS_PER_PIXEL, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_PIXEL_X_DIMENSION, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_PIXEL_Y_DIMENSION, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_MAKER_NOTE, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_USER_COMMENT, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_RELATED_SOUND_FILE, exifFlags | ExifTag.TYPE_ASCII << 16 | 13);
        mTagInfo.put(ExifInterface.TAG_DATE_TIME_ORIGINAL, exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
        mTagInfo.put(ExifInterface.TAG_DATE_TIME_DIGITIZED, exifFlags | ExifTag.TYPE_ASCII << 16 | 20);
        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_ORIGINAL, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_SUB_SEC_TIME_DIGITIZED, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_IMAGE_UNIQUE_ID, exifFlags | ExifTag.TYPE_ASCII << 16 | 33);
        mTagInfo.put(ExifInterface.TAG_LENS_SPECS, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 4);
        mTagInfo.put(ExifInterface.TAG_LENS_MAKE, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_LENS_MODEL, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_SENSITIVITY_TYPE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_EXPOSURE_TIME, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_F_NUMBER, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_EXPOSURE_PROGRAM, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SPECTRAL_SENSITIVITY, exifFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_ISO_SPEED_RATINGS, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16);
        mTagInfo.put(ExifInterface.TAG_OECF, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_SHUTTER_SPEED_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_APERTURE_VALUE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_BRIGHTNESS_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_EXPOSURE_BIAS_VALUE, exifFlags | ExifTag.TYPE_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_MAX_APERTURE_VALUE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_METERING_MODE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_LIGHT_SOURCE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FLASH, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SUBJECT_AREA, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16);
        mTagInfo.put(ExifInterface.TAG_FLASH_ENERGY, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SPATIAL_FREQUENCY_RESPONSE, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_X_RESOLUTION, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_Y_RESOLUTION, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FOCAL_PLANE_RESOLUTION_UNIT, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SUBJECT_LOCATION, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_EXPOSURE_INDEX, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SENSING_METHOD, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FILE_SOURCE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SCENE_TYPE, exifFlags | ExifTag.TYPE_UNDEFINED << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_CFA_PATTERN, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_CUSTOM_RENDERED, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_EXPOSURE_MODE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_WHITE_BALANCE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_DIGITAL_ZOOM_RATIO, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_FOCAL_LENGTH_IN_35_MM_FILE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SCENE_CAPTURE_TYPE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GAIN_CONTROL, exifFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_CONTRAST, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SATURATION, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_SHARPNESS, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_DEVICE_SETTING_DESCRIPTION, exifFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_SUBJECT_DISTANCE_RANGE, exifFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_INTEROPERABILITY_IFD, exifFlags | ExifTag.TYPE_UNSIGNED_LONG << 16 | 1);
        // GPS tag
        int[] gpsAllowedIfds = {IfdId.TYPE_IFD_GPS};
        int gpsFlags = getFlagsFromAllowedIfds(gpsAllowedIfds) << 24;
        mTagInfo.put(ExifInterface.TAG_GPS_VERSION_ID, gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 4);
        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_LATITUDE, gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
        mTagInfo.put(ExifInterface.TAG_GPS_LONGITUDE, gpsFlags | ExifTag.TYPE_RATIONAL << 16 | 3);
        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE_REF, gpsFlags | ExifTag.TYPE_UNSIGNED_BYTE << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_ALTITUDE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_TIME_STAMP, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 3);
        mTagInfo.put(ExifInterface.TAG_GPS_SATTELLITES, gpsFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_GPS_STATUS, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_MEASURE_MODE, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_DOP, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_SPEED_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_SPEED, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_TRACK_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_TRACK, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_IMG_DIRECTION, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_MAP_DATUM, gpsFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_LATITUDE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_BEARING, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE_REF, gpsFlags | ExifTag.TYPE_ASCII << 16 | 2);
        mTagInfo.put(ExifInterface.TAG_GPS_DEST_DISTANCE, gpsFlags | ExifTag.TYPE_UNSIGNED_RATIONAL << 16 | 1);
        mTagInfo.put(ExifInterface.TAG_GPS_PROCESSING_METHOD, gpsFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_GPS_AREA_INFORMATION, gpsFlags | ExifTag.TYPE_UNDEFINED << 16);
        mTagInfo.put(ExifInterface.TAG_GPS_DATE_STAMP, gpsFlags | ExifTag.TYPE_ASCII << 16 | 11);
        mTagInfo.put(ExifInterface.TAG_GPS_DIFFERENTIAL, gpsFlags | ExifTag.TYPE_UNSIGNED_SHORT << 16 | 11);
        // Interoperability tag
        int[] interopAllowedIfds = {IfdId.TYPE_IFD_INTEROPERABILITY};
        int interopFlags = getFlagsFromAllowedIfds(interopAllowedIfds) << 24;
        mTagInfo.put(TAG_INTEROPERABILITY_INDEX, interopFlags | ExifTag.TYPE_ASCII << 16);
        mTagInfo.put(TAG_INTEROP_VERSION, interopFlags | ExifTag.TYPE_UNDEFINED << 16 | 4);
    }

    /**
     * Returns the value of the ExifTag in that tag's default IFD for a defined
     * tag constant or null if none exists or the value could not be cast into
     * the return type.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return the value of the ExifTag or null if none exists.
     */
    @SuppressWarnings("unused")
    public Object getTagValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagValue(tagId, ifdId);
    }

    /**
     * Gets a tag value for an IFD other than the tag's default.
     *
     * @see #getTagValue
     */
    public Object getTagValue(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        return (t == null) ? null : t.getValue();
    }

    /**
     * @see #getTagValue
     */
    public String getTagStringValue(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return null;
        }
        return t.getValueAsString();
    }

    /**
     * @see #getTagValue
     */
    public String getTagStringValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagStringValue(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    @SuppressWarnings("unused")
    public Long getTagLongValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagLongValue(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    public Long getTagLongValue(int tagId, int ifdId) {
        long[] l = getTagLongValues(tagId, ifdId);
        if (l == null || l.length <= 0) {
            return null;
        }
        return new Long(l[0]);
    }

    /*
     * Getter methods that are similar to getTagValue. Null is returned if the
     * tag value cannot be cast into the return type.
     */

    /**
     * @see #getTagValue
     */
    public long[] getTagLongValues(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return null;
        }
        return t.getValueAsLongs();
    }

    /**
     * @see #getTagValue
     */
    public Integer getTagIntValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagIntValue(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    public Integer getTagIntValue(int tagId, int ifdId) {
        int[] l = getTagIntValues(tagId, ifdId);
        if (l == null || l.length <= 0) {
            return null;
        }
        return new Integer(l[0]);
    }

    /**
     * @see #getTagValue
     */
    public int[] getTagIntValues(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return null;
        }
        return t.getValueAsInts();
    }

    /**
     * @see #getTagValue
     */
    public Byte getTagByteValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagByteValue(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    public Byte getTagByteValue(int tagId, int ifdId) {
        byte[] l = getTagByteValues(tagId, ifdId);
        if (l == null || l.length <= 0) {
            return null;
        }
        return new Byte(l[0]);
    }

    /**
     * @see #getTagValue
     */
    public byte[] getTagByteValues(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return null;
        }
        return t.getValueAsBytes();
    }

    /**
     * @see #getTagValue
     */
    public Rational getTagRationalValue(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagRationalValue(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    public Rational getTagRationalValue(int tagId, int ifdId) {
        Rational[] l = getTagRationalValues(tagId, ifdId);
        if (l == null || l.length == 0) {
            return null;
        }
        return new Rational(l[0]);
    }

    /**
     * @see #getTagValue
     */
    public Rational[] getTagRationalValues(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return null;
        }
        return t.getValueAsRationals();
    }

    /**
     * @see #getTagValue
     */
    @SuppressWarnings("unused")
    public long[] getTagLongValues(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagLongValues(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    @SuppressWarnings("unused")
    public int[] getTagIntValues(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagIntValues(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    @SuppressWarnings("unused")
    public byte[] getTagByteValues(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagByteValues(tagId, ifdId);
    }

    /**
     * @see #getTagValue
     */
    public Rational[] getTagRationalValues(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return getTagRationalValues(tagId, ifdId);
    }

    /**
     * Checks whether a tag has a defined number of elements.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return true if the tag has a defined number of elements.
     */
    @SuppressWarnings("unused")
    public boolean isTagCountDefined(int tagId) {
        int info = getTagInfo().get(tagId);
        // No value in info can be zero, as all tags have a non-zero type
        return info != 0 && getComponentCountFromInfo(info) != ExifTag.SIZE_UNDEFINED;
    }

    /**
     * Gets the defined number of elements for a tag.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return the number of elements or {@link ExifTag#SIZE_UNDEFINED} if the
     * tag or the number of elements is not defined.
     */
    @SuppressWarnings("unused")
    public int getDefinedTagCount(int tagId) {
        int info = getTagInfo().get(tagId);
        if (info == 0) {
            return ExifTag.SIZE_UNDEFINED;
        }
        return getComponentCountFromInfo(info);
    }

    /**
     * Gets the number of elements for an ExifTag in a given IFD.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param ifdId the IFD containing the ExifTag to check.
     * @return the number of elements in the ExifTag, if the tag's size is
     * undefined this will return the actual number of elements that is
     * in the ExifTag's value.
     */
    @SuppressWarnings("unused")
    public int getActualTagCount(int tagId, int ifdId) {
        ExifTag t = getTag(tagId, ifdId);
        if (t == null) {
            return 0;
        }
        return t.getComponentCount();
    }

    /**
     * Gets the defined type for a tag.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @return the type.
     * @see ExifTag#getDataType()
     */
    @SuppressWarnings("unused")
    public short getDefinedTagType(int tagId) {
        int info = getTagInfo().get(tagId);
        if (info == 0) {
            return -1;
        }
        return getTypeFromInfo(info);
    }

    protected ExifTag buildUninitializedTag(int tagId) {
        int info = getTagInfo().get(tagId);
        if (info == 0) {
            return null;
        }
        short type = getTypeFromInfo(info);
        int definedCount = getComponentCountFromInfo(info);
        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
        int ifdId = getTrueIfd(tagId);
        return new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
    }

    /**
     * Sets the value of an ExifTag if it exists it's default IFD. The value
     * must be the correct type and length for that ExifTag.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param val   the value to set.
     * @return true if success, false if the ExifTag doesn't exist or the value
     * is the wrong type/length.
     */
    @SuppressWarnings("unused")
    public boolean setTagValue(int tagId, Object val) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        return setTagValue(tagId, ifdId, val);
    }

    /**
     * Sets the value of an ExifTag if it exists in the given IFD. The value
     * must be the correct type and length for that ExifTag.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param ifdId the IFD that the ExifTag is in.
     * @param val   the value to set.
     * @return true if success, false if the ExifTag doesn't exist or the value
     * is the wrong type/length.
     * @see #setTagValue
     */
    public boolean setTagValue(int tagId, int ifdId, Object val) {
        ExifTag t = getTag(tagId, ifdId);
        return t != null && t.setValue(val);
    }

    /**
     * Removes the ExifTag for a tag constant from that tag's default IFD.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     */
    public void deleteTag(int tagId) {
        int ifdId = getDefinedTagDefaultIfd(tagId);
        deleteTag(tagId, ifdId);
    }

    /**
     * Removes the ExifTag for a tag constant from the given IFD.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param ifdId the IFD of the ExifTag to remove.
     */
    public void deleteTag(int tagId, int ifdId) {
        mData.removeTag(getTrueTagKey(tagId), ifdId);
    }

    /**
     * Creates a new tag definition in this ExifInterface object for a given TID
     * and default IFD. Creating a definition with the same TID and default IFD
     * as a previous definition will override it.
     *
     * @param tagId                 the TID for the tag.
     * @param defaultIfd            the default IFD for the tag.
     * @param tagType               the type of the tag (see {@link ExifTag#getDataType()}).
     * @param defaultComponentCount the number of elements of this tag's type in
     *                              the tags value.
     * @param allowedIfds           the IFD's this tag is allowed to be put in.
     * @return the defined tag constant (e.g. {@link #TAG_IMAGE_WIDTH}) or
     * {@link #TAG_NULL} if the definition could not be made.
     */
    @SuppressWarnings("unused")
    public int setTagDefinition(
            short tagId, int defaultIfd, short tagType, short defaultComponentCount, int[] allowedIfds) {
        if (sBannedDefines.contains(tagId)) {
            return TAG_NULL;
        }
        if (ExifTag.isValidType(tagType) && ExifTag.isValidIfd(defaultIfd)) {
            int tagDef = defineTag(defaultIfd, tagId);
            if (tagDef == TAG_NULL) {
                return TAG_NULL;
            }
            int[] otherDefs = getTagDefinitionsForTagId(tagId);
            SparseIntArray infos = getTagInfo();
            // Make sure defaultIfd is in allowedIfds
            boolean defaultCheck = false;
            for (int i : allowedIfds) {
                if (defaultIfd == i) {
                    defaultCheck = true;
                }
                if (!ExifTag.isValidIfd(i)) {
                    return TAG_NULL;
                }
            }
            if (!defaultCheck) {
                return TAG_NULL;
            }

            int ifdFlags = getFlagsFromAllowedIfds(allowedIfds);
            // Make sure no identical tags can exist in allowedIfds
            if (otherDefs != null) {
                for (int def : otherDefs) {
                    int tagInfo = infos.get(def);
                    int allowedFlags = getAllowedIfdFlagsFromInfo(tagInfo);
                    if ((ifdFlags & allowedFlags) != 0) {
                        return TAG_NULL;
                    }
                }
            }
            getTagInfo().put(tagDef, ifdFlags << 24 | (tagType << 16) | defaultComponentCount);
            return tagDef;
        }
        return TAG_NULL;
    }

    @SuppressWarnings("unused")
    protected int getTagDefinition(short tagId, int defaultIfd) {
        return getTagInfo().get(defineTag(defaultIfd, tagId));
    }

    protected int[] getTagDefinitionsForTagId(short tagId) {
        int[] ifds = IfdData.getIfds();
        int[] defs = new int[ifds.length];
        int counter = 0;
        SparseIntArray infos = getTagInfo();
        for (int i : ifds) {
            int def = defineTag(i, tagId);
            if (infos.get(def) != DEFINITION_NULL) {
                defs[counter++] = def;
            }
        }
        if (counter == 0) {
            return null;
        }

        return Arrays.copyOfRange(defs, 0, counter);
    }

    @SuppressWarnings("unused")
    protected int getTagDefinitionForTag(ExifTag tag) {
        short type = tag.getDataType();
        int count = tag.getComponentCount();
        int ifd = tag.getIfd();
        return getTagDefinitionForTag(tag.getTagId(), type, count, ifd);
    }

    protected int getTagDefinitionForTag(short tagId, short type, int count, int ifd) {
        int[] defs = getTagDefinitionsForTagId(tagId);
        if (defs == null) {
            return TAG_NULL;
        }
        SparseIntArray infos = getTagInfo();
        int ret = TAG_NULL;
        for (int i : defs) {
            int info = infos.get(i);
            short def_type = getTypeFromInfo(info);
            int def_count = getComponentCountFromInfo(info);
            int[] def_ifds = getAllowedIfdsFromInfo(info);
            boolean valid_ifd = false;
            for (int j : def_ifds) {
                if (j == ifd) {
                    valid_ifd = true;
                    break;
                }
            }
            if (valid_ifd && type == def_type && (count == def_count || def_count == ExifTag.SIZE_UNDEFINED)) {
                ret = i;
                break;
            }
        }
        return ret;
    }

    /**
     * Removes a tag definition for given defined tag constant.
     *
     * @param tagId a defined tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     */
    @SuppressWarnings("unused")
    public void removeTagDefinition(int tagId) {
        getTagInfo().delete(tagId);
    }

    /**
     * Resets tag definitions to the default ones.
     */
    @SuppressWarnings("unused")
    public void resetTagDefinitions() {
        mTagInfo = null;
    }

    /**
     * Returns the thumbnail from IFD1 as a bitmap, or null if none exists.
     *
     * @return the thumbnail as a bitmap.
     */
    public Bitmap getThumbnailBitmap() {
        if (mData.hasCompressedThumbnail()) {
            byte[] thumb = mData.getCompressedThumbnail();
            return BitmapFactory.decodeByteArray(thumb, 0, thumb.length);
        } else if (mData.hasUncompressedStrip()) {
            // TODO: implement uncompressed
        }
        return null;
    }

    /**
     * Returns the thumbnail from IFD1 as a byte array, or null if none exists.
     * The bytes may either be an uncompressed strip as specified in the exif
     * standard or a jpeg compressed image.
     *
     * @return the thumbnail as a byte array.
     */
    @SuppressWarnings("unused")
    public byte[] getThumbnailBytes() {
        if (mData.hasCompressedThumbnail()) {
            return mData.getCompressedThumbnail();
        } else if (mData.hasUncompressedStrip()) {
            // TODO: implement this
        }
        return null;
    }

    /**
     * Returns the thumbnail if it is jpeg compressed, or null if none exists.
     *
     * @return the thumbnail as a byte array.
     */
    public byte[] getThumbnail() {
        return mData.getCompressedThumbnail();
    }

    /**
     * Returns the JPEG quality used to generate the image
     * or 0 if not found
     *
     * @return
     */
    public int getQualityGuess() {
        return mData.getQualityGuess();
    }

    /**
     * this gives information about the process used to create the JPEG file.
     * Possible values are:
     * <ul>
     * <li>'0' Unknown</li>
     * <li>'192' Baseline</li>
     * <li>'193' Extended sequential</li>
     * <li>'194' Progressive</li>
     * <li>'195' Lossless</li>
     * <li>'197' Differential sequential</li>
     * <li>'198' Differential progressive</li>
     * <li>'199' Differential lossless</li>
     * <li>'201' Extended sequential, arithmetic coding</li>
     * <li>'202' Progressive, arithmetic coding</li>
     * <li>'203' Lossless, arithmetic coding</li>
     * <li>'205' Differential sequential, arithmetic coding</li>
     * <li>'206' Differential progressive, arithmetic codng</li>
     * <li>'207' Differential lossless, arithmetic coding</li>
     * </ul>
     */
    public short getJpegProcess() {
        return mData.getJpegProcess();
    }

    /**
     * Returns the Image size as decoded from the SOF marker
     */
    public int[] getImageSize() {
        return mData.getImageSize();
    }

    /**
     * Check if thumbnail is compressed.
     *
     * @return true if the thumbnail is compressed.
     */
    @SuppressWarnings("unused")
    public boolean isThumbnailCompressed() {
        return mData.hasCompressedThumbnail();
    }

    /**
     * Check if thumbnail exists.
     *
     * @return true if a compressed thumbnail exists.
     */
    public boolean hasThumbnail() {
        // TODO: add back in uncompressed strip
        return mData.hasCompressedThumbnail();
    }

    /**
     * Sets the thumbnail to be a jpeg compressed bitmap. Clears any prior
     * thumbnail.
     *
     * @param thumb a bitmap to compress to a jpeg thumbnail.
     * @return true if the thumbnail was set.
     */
    @SuppressWarnings("unused")
    public boolean setCompressedThumbnail(Bitmap thumb) {
        ByteArrayOutputStream thumbnail = new ByteArrayOutputStream();
        if (!thumb.compress(Bitmap.CompressFormat.JPEG, 90, thumbnail)) {
            return false;
        }
        return setCompressedThumbnail(thumbnail.toByteArray());
    }

    /**
     * Sets the thumbnail to be a jpeg compressed image. Clears any prior
     * thumbnail.
     *
     * @param thumb a byte array containing a jpeg compressed image.
     * @return true if the thumbnail was set.
     */
    public boolean setCompressedThumbnail(byte[] thumb) {
        mData.clearThumbnailAndStrips();
        mData.setCompressedThumbnail(thumb);
        return true;
    }

    /**
     * Clears the compressed thumbnail if it exists.
     */
    @SuppressWarnings("unused")
    public void removeCompressedThumbnail() {
        mData.setCompressedThumbnail(null);
    }

    /**
     * Decodes the user comment tag into string as specified in the EXIF
     * standard. Returns null if decoding failed.
     */
    @SuppressWarnings("unused")
    public String getUserComment() {
        return mData.getUserComment();
    }

    /**
     * Return the altitude in meters. If the exif tag does not exist, return
     * <var>defaultValue</var>.
     *
     * @param defaultValue the value to return if the tag is not available.
     */
    @SuppressWarnings("unused")
    public double getAltitude(double defaultValue) {

        Byte ref = getTagByteValue(TAG_GPS_ALTITUDE_REF);
        Rational gpsAltitude = getTagRationalValue(TAG_GPS_ALTITUDE);

        int seaLevel = 1;
        if (null != ref) {
            seaLevel = ref.intValue() == 1 ? -1 : 1;
        }

        if (gpsAltitude != null) {
            return gpsAltitude.toDouble() * seaLevel;
        }

        return defaultValue;
    }

    /**
     * Gets the GPS latitude and longitude as a pair of doubles from this
     * ExifInterface object's tags, or null if the necessary tags do not exist.
     *
     * @return an array of 2 doubles containing the latitude, and longitude
     * respectively.
     * @see #convertLatOrLongToDouble
     */
    public double[] getLatLongAsDoubles() {
        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);
        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);
        if (latitude == null || longitude == null || latitudeRef == null || longitudeRef == null || latitude.length < 3 || longitude.length < 3) {
            return null;
        }
        double[] latLon = new double[2];
        latLon[0] = convertLatOrLongToDouble(latitude, latitudeRef);
        latLon[1] = convertLatOrLongToDouble(longitude, longitudeRef);
        return latLon;
    }

    /**
     * Returns a formatted String with the latitude representation:<br />
     * 39° 8' 16.8" N
     */
    public String getLatitude() {
        Rational[] latitude = getTagRationalValues(TAG_GPS_LATITUDE);
        String latitudeRef = getTagStringValue(TAG_GPS_LATITUDE_REF);

        if (null == latitude || null == latitudeRef) return null;
        return convertRationalLatLonToString(latitude, latitudeRef);
    }

    /**
     * Returns a formatted String with the longitude representation:<br />
     * 77° 37' 51.6" W
     */
    public String getLongitude() {
        Rational[] longitude = getTagRationalValues(TAG_GPS_LONGITUDE);
        String longitudeRef = getTagStringValue(TAG_GPS_LONGITUDE_REF);

        if (null == longitude || null == longitudeRef) return null;
        return convertRationalLatLonToString(longitude, longitudeRef);
    }

    /**
     * Creates, formats, and sets the DateTimeStamp tag for one of:
     * {@link #TAG_DATE_TIME}, {@link #TAG_DATE_TIME_DIGITIZED},
     * {@link #TAG_DATE_TIME_ORIGINAL}.
     *
     * @param tagId     one of the DateTimeStamp tags.
     * @param timestamp a timestamp to format.
     * @param timezone  a TimeZone object.
     * @return true if success, false if the tag could not be set.
     */
    public boolean addDateTimeStampTag(int tagId, long timestamp, TimeZone timezone) {
        if (tagId == TAG_DATE_TIME || tagId == TAG_DATE_TIME_DIGITIZED || tagId == TAG_DATE_TIME_ORIGINAL) {
            mDateTimeStampFormat.setTimeZone(timezone);
            ExifTag t = buildTag(tagId, mDateTimeStampFormat.format(timestamp));
            if (t == null) {
                return false;
            }
            setTag(t);
        } else {
            return false;
        }
        return true;
    }

    /**
     * Creates a tag for a defined tag constant in the tag's default IFD.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param val   the tag's value.
     * @return an ExifTag object.
     */
    public ExifTag buildTag(int tagId, Object val) {
        int ifdId = getTrueIfd(tagId);
        return buildTag(tagId, ifdId, val);
    }

    /**
     * Creates a tag for a defined tag constant in a given IFD if that IFD is
     * allowed for the tag.  This method will fail anytime the appropriate
     * {@link ExifTag#setValue} for this tag's datatype would fail.
     *
     * @param tagId a tag constant, e.g. {@link #TAG_IMAGE_WIDTH}.
     * @param ifdId the IFD that the tag should be in.
     * @param val   the value of the tag to set.
     * @return an ExifTag object or null if one could not be constructed.
     * @see #buildTag
     */
    public ExifTag buildTag(int tagId, int ifdId, Object val) {
        int info = getTagInfo().get(tagId);
        if (info == 0 || val == null) {
            return null;
        }
        short type = getTypeFromInfo(info);
        int definedCount = getComponentCountFromInfo(info);
        boolean hasDefinedCount = (definedCount != ExifTag.SIZE_UNDEFINED);
        if (!ExifInterface.isIfdAllowed(info, ifdId)) {
            return null;
        }
        ExifTag t = new ExifTag(getTrueTagKey(tagId), type, definedCount, ifdId, hasDefinedCount);
        if (!t.setValue(val)) {
            return null;
        }
        return t;
    }

    /**
     * Creates and sets all to the GPS tags for a give latitude and longitude.
     *
     * @param latitude  a GPS latitude coordinate.
     * @param longitude a GPS longitude coordinate.
     * @return true if success, false if they could not be created or set.
     */
    @SuppressWarnings("unused")
    public boolean addGpsTags(double latitude, double longitude) {
        ExifTag latTag = buildTag(TAG_GPS_LATITUDE, toExifLatLong(latitude));
        ExifTag longTag = buildTag(TAG_GPS_LONGITUDE, toExifLatLong(longitude));
        ExifTag latRefTag = buildTag(TAG_GPS_LATITUDE_REF, latitude >= 0 ? GpsLatitudeRef.NORTH : GpsLatitudeRef.SOUTH);
        ExifTag longRefTag = buildTag(TAG_GPS_LONGITUDE_REF, longitude >= 0 ? GpsLongitudeRef.EAST : GpsLongitudeRef.WEST);
        if (latTag == null || longTag == null || latRefTag == null || longRefTag == null) {
            return false;
        }
        setTag(latTag);
        setTag(longTag);
        setTag(latRefTag);
        setTag(longRefTag);
        return true;
    }

    /**
     * Creates and sets the GPS timestamp tag.
     *
     * @param timestamp a GPS timestamp.
     * @return true if success, false if could not be created or set.
     */
    @SuppressWarnings("unused")
    public boolean addGpsDateTimeStampTag(long timestamp) {
        ExifTag t = buildTag(TAG_GPS_DATE_STAMP, mGPSDateStampFormat.format(timestamp));
        if (t == null) {
            return false;
        }
        setTag(t);
        mGPSTimeStampCalendar.setTimeInMillis(timestamp);
        t = buildTag(TAG_GPS_TIME_STAMP,
                new Rational[]{new Rational(mGPSTimeStampCalendar.get(Calendar.HOUR_OF_DAY), 1), new Rational(mGPSTimeStampCalendar.get(Calendar.MINUTE), 1),
                        new Rational(mGPSTimeStampCalendar.get(Calendar.SECOND), 1)}
        );
        if (t == null) {
            return false;
        }
        setTag(t);
        return true;
    }

    /**
     * Return the aperture size, if present, 0 if missing
     */
    public double getApertureSize() {
        Rational rational = getTagRationalValue(TAG_F_NUMBER);
        if (null != rational && rational.toDouble() > 0) {
            return rational.toDouble();
        }

        rational = getTagRationalValue(TAG_APERTURE_VALUE);
        if (null != rational && rational.toDouble() > 0) {
            return Math.exp(rational.toDouble() * Math.log(2) * 0.5);
        }
        return 0;
    }

    /**
     * Returns the lens model as string if any of the tags {@link #TAG_LENS_MODEL}
     * or {@link #TAG_LENS_SPECS} are found
     *
     * @return the string representation of the lens spec
     */
    public String getLensModelDescription() {
        String lensModel = getTagStringValue(TAG_LENS_MODEL);
        if (null != lensModel) return lensModel;

        Rational[] rat = getTagRationalValues(TAG_LENS_SPECS);
        if (null != rat) return ExifUtil.processLensSpecifications(rat);

        return null;
    }

    /**
     * Constants for {@link #TAG_ORIENTATION}. They can be interpreted as
     * follows:
     * <ul>
     * <li>TOP_LEFT is the normal orientation.</li>
     * <li>TOP_RIGHT is a left-right mirror.</li>
     * <li>BOTTOM_LEFT is a 180 degree rotation.</li>
     * <li>BOTTOM_RIGHT is a top-bottom mirror.</li>
     * <li>LEFT_TOP is mirrored about the top-left<->bottom-right axis.</li>
     * <li>RIGHT_TOP is a 90 degree clockwise rotation.</li>
     * <li>LEFT_BOTTOM is mirrored about the top-right<->bottom-left axis.</li>
     * <li>RIGHT_BOTTOM is a 270 degree clockwise rotation.</li>
     * </ul>
     */
    @SuppressWarnings("unused")
    public static interface Orientation {
        public static final short TOP_LEFT = 1;
        public static final short TOP_RIGHT = 2;
        public static final short BOTTOM_RIGHT = 3;
        public static final short BOTTOM_LEFT = 4;
        public static final short LEFT_TOP = 5;
        public static final short RIGHT_TOP = 6;
        public static final short RIGHT_BOTTOM = 7;
        public static final short LEFT_BOTTOM = 8;
    }

    /**
     * Constants for {@link #TAG_Y_CB_CR_POSITIONING}
     */
    @SuppressWarnings("unused")
    public static interface YCbCrPositioning {
        public static final short CENTERED = 1;
        public static final short CO_SITED = 2;
    }

    // TODO: uncompressed thumbnail setters

    /**
     * Constants for {@link #TAG_COMPRESSION}
     */
    @SuppressWarnings("unused")
    public static interface Compression {
        public static final short UNCOMPRESSION = 1;
        public static final short JPEG = 6;
    }

    /**
     * Constants for {@link #TAG_RESOLUTION_UNIT}
     */
    @SuppressWarnings("unused")
    public static interface ResolutionUnit {
        public static final short INCHES = 2;
        public static final short CENTIMETERS = 3;
        public static final short MILLIMETERS = 4;
        public static final short MICROMETERS = 5;
    }

    /**
     * Constants for {@link #TAG_PHOTOMETRIC_INTERPRETATION}
     */
    @SuppressWarnings("unused")
    public static interface PhotometricInterpretation {
        public static final short RGB = 2;
        public static final short YCBCR = 6;
    }

    // Convenience methods:

    /**
     * Constants for {@link #TAG_PLANAR_CONFIGURATION}
     */
    @SuppressWarnings("unused")
    public static interface PlanarConfiguration {
        public static final short CHUNKY = 1;
        public static final short PLANAR = 2;
    }

    /**
     * Constants for {@link #TAG_EXPOSURE_PROGRAM}
     */
    @SuppressWarnings("unused")
    public static interface ExposureProgram {
        public static final short NOT_DEFINED = 0;
        public static final short MANUAL = 1;
        public static final short NORMAL_PROGRAM = 2;
        public static final short APERTURE_PRIORITY = 3;
        public static final short SHUTTER_PRIORITY = 4;
        public static final short CREATIVE_PROGRAM = 5;
        public static final short ACTION_PROGRAM = 6;
        public static final short PROTRAIT_MODE = 7;
        public static final short LANDSCAPE_MODE = 8;
    }

    /**
     * Constants for {@link #TAG_METERING_MODE}
     */
    @SuppressWarnings("unused")
    public static interface MeteringMode {
        public static final short UNKNOWN = 0;
        public static final short AVERAGE = 1;
        public static final short CENTER_WEIGHTED_AVERAGE = 2;
        public static final short SPOT = 3;
        public static final short MULTISPOT = 4;
        public static final short PATTERN = 5;
        public static final short PARTAIL = 6;
        public static final short OTHER = 255;
    }

    /**
     * Constants for {@link #TAG_FLASH} As the definition in Jeita EXIF 2.2
     */
    @SuppressWarnings("unused")
    public static interface Flash {

        /**
         * first bit
         */
        public static enum FlashFired {
            NO, YES
        }

        /**
         * Values for bits 1 and 2 indicating the status of returned light
         */
        public static enum StrobeLightDetection {
            NO_DETECTION, RESERVED, LIGHT_NOT_DETECTED, LIGHT_DETECTED
        }

        /**
         * Values for bits 3 and 4 indicating the camera's flash mode
         */
        public static enum CompulsoryMode {
            UNKNOWN,
            FIRING,
            SUPPRESSION,
            AUTO
        }

        /**
         * Values for bit 5 indicating the presence of a flash function.
         */
        public static enum FlashFunction {
            FUNCTION_PRESENT,
            FUNCTION_NOR_PRESENT
        }

        /**
         * Values for bit 6 indicating the camera's red-eye mode.
         */
        public static enum RedEyeMode {
            NONE,
            SUPPORTED
        }
    }

    /**
     * Constants for {@link #TAG_COLOR_SPACE}
     */
    @SuppressWarnings("unused")
    public static interface ColorSpace {
        public static final short SRGB = 1;
        public static final short UNCALIBRATED = (short) 0xFFFF;
    }

    /**
     * Constants for {@link #TAG_EXPOSURE_MODE}
     */
    @SuppressWarnings("unused")
    public static interface ExposureMode {
        public static final short AUTO_EXPOSURE = 0;
        public static final short MANUAL_EXPOSURE = 1;
        public static final short AUTO_BRACKET = 2;
    }

    /**
     * Constants for {@link #TAG_WHITE_BALANCE}
     */
    @SuppressWarnings("unused")
    public static interface WhiteBalance {
        public static final short AUTO = 0;
        public static final short MANUAL = 1;
    }

    /**
     * Constants for {@link #TAG_SCENE_CAPTURE_TYPE}
     */
    @SuppressWarnings("unused")
    public static interface SceneCapture {
        public static final short STANDARD = 0;
        public static final short LANDSCAPE = 1;
        public static final short PROTRAIT = 2;
        public static final short NIGHT_SCENE = 3;
    }

    /**
     * Constants for {@link #TAG_COMPONENTS_CONFIGURATION}
     */
    @SuppressWarnings("unused")
    public static interface ComponentsConfiguration {
        public static final short NOT_EXIST = 0;
        public static final short Y = 1;
        public static final short CB = 2;
        public static final short CR = 3;
        public static final short R = 4;
        public static final short G = 5;
        public static final short B = 6;
    }

    /**
     * Constants for {@link #TAG_LIGHT_SOURCE}
     */
    @SuppressWarnings("unused")
    public static interface LightSource {
        public static final short UNKNOWN = 0;
        public static final short DAYLIGHT = 1;
        public static final short FLUORESCENT = 2;
        public static final short TUNGSTEN = 3;
        public static final short FLASH = 4;
        public static final short FINE_WEATHER = 9;
        public static final short CLOUDY_WEATHER = 10;
        public static final short SHADE = 11;
        public static final short DAYLIGHT_FLUORESCENT = 12;
        public static final short DAY_WHITE_FLUORESCENT = 13;
        public static final short COOL_WHITE_FLUORESCENT = 14;
        public static final short WHITE_FLUORESCENT = 15;
        public static final short STANDARD_LIGHT_A = 17;
        public static final short STANDARD_LIGHT_B = 18;
        public static final short STANDARD_LIGHT_C = 19;
        public static final short D55 = 20;
        public static final short D65 = 21;
        public static final short D75 = 22;
        public static final short D50 = 23;
        public static final short ISO_STUDIO_TUNGSTEN = 24;
        public static final short OTHER = 255;
    }

    /**
     * Constants for {@link #TAG_SENSING_METHOD}
     */
    @SuppressWarnings("unused")
    public static interface SensingMethod {
        public static final short NOT_DEFINED = 1;
        public static final short ONE_CHIP_COLOR = 2;
        public static final short TWO_CHIP_COLOR = 3;
        public static final short THREE_CHIP_COLOR = 4;
        public static final short COLOR_SEQUENTIAL_AREA = 5;
        public static final short TRILINEAR = 7;
        public static final short COLOR_SEQUENTIAL_LINEAR = 8;
    }

    /**
     * Constants for {@link #TAG_FILE_SOURCE}
     */
    @SuppressWarnings("unused")
    public static interface FileSource {
        public static final short DSC = 3;
    }

    /**
     * Constants for {@link #TAG_SCENE_TYPE}
     */
    @SuppressWarnings("unused")
    public static interface SceneType {
        public static final short DIRECT_PHOTOGRAPHED = 1;
    }

    /**
     * Constants for {@link #TAG_GAIN_CONTROL}
     */
    @SuppressWarnings("unused")
    public static interface GainControl {
        public static final short NONE = 0;
        public static final short LOW_UP = 1;
        public static final short HIGH_UP = 2;
        public static final short LOW_DOWN = 3;
        public static final short HIGH_DOWN = 4;
    }

    /**
     * Constants for {@link #TAG_CONTRAST}
     */
    @SuppressWarnings("unused")
    public static interface Contrast {
        public static final short NORMAL = 0;
        public static final short SOFT = 1;
        public static final short HARD = 2;
    }

    /**
     * Constants for {@link #TAG_SATURATION}
     */
    @SuppressWarnings("unused")
    public static interface Saturation {
        public static final short NORMAL = 0;
        public static final short LOW = 1;
        public static final short HIGH = 2;
    }

    /**
     * Constants for {@link #TAG_SHARPNESS}
     */
    @SuppressWarnings("unused")
    public static interface Sharpness {
        public static final short NORMAL = 0;
        public static final short SOFT = 1;
        public static final short HARD = 2;
    }

    /**
     * Constants for {@link #TAG_SUBJECT_DISTANCE}
     */
    @SuppressWarnings("unused")
    public static interface SubjectDistance {
        public static final short UNKNOWN = 0;
        public static final short MACRO = 1;
        public static final short CLOSE_VIEW = 2;
        public static final short DISTANT_VIEW = 3;
    }

    /**
     * Constants for {@link #TAG_GPS_LATITUDE_REF},
     * {@link #TAG_GPS_DEST_LATITUDE_REF}
     */
    @SuppressWarnings("unused")
    public static interface GpsLatitudeRef {
        public static final String NORTH = "N";
        public static final String SOUTH = "S";
    }

    /**
     * Constants for {@link #TAG_GPS_LONGITUDE_REF},
     * {@link #TAG_GPS_DEST_LONGITUDE_REF}
     */
    @SuppressWarnings("unused")
    public static interface GpsLongitudeRef {
        public static final String EAST = "E";
        public static final String WEST = "W";
    }

    /**
     * Constants for {@link #TAG_GPS_ALTITUDE_REF}
     */
    @SuppressWarnings("unused")
    public static interface GpsAltitudeRef {
        public static final short SEA_LEVEL = 0;
        public static final short SEA_LEVEL_NEGATIVE = 1;
    }

    /**
     * Constants for {@link #TAG_GPS_STATUS}
     */
    @SuppressWarnings("unused")
    public static interface GpsStatus {
        public static final String IN_PROGRESS = "A";
        public static final String INTEROPERABILITY = "V";
    }

    /**
     * Constants for {@link #TAG_GPS_MEASURE_MODE}
     */
    @SuppressWarnings("unused")
    public static interface GpsMeasureMode {
        public static final String MODE_2_DIMENSIONAL = "2";
        public static final String MODE_3_DIMENSIONAL = "3";
    }

    /**
     * Constants for {@link #TAG_GPS_SPEED_REF},
     * {@link #TAG_GPS_DEST_DISTANCE_REF}
     */
    @SuppressWarnings("unused")
    public static interface GpsSpeedRef {
        public static final String KILOMETERS = "K";
        public static final String MILES = "M";
        public static final String KNOTS = "N";
    }

    /**
     * Constants for {@link #TAG_GPS_TRACK_REF},
     * {@link #TAG_GPS_IMG_DIRECTION_REF}, {@link #TAG_GPS_DEST_BEARING_REF}
     */
    @SuppressWarnings("unused")
    public static interface GpsTrackRef {
        public static final String TRUE_DIRECTION = "T";
        public static final String MAGNETIC_DIRECTION = "M";
    }

    /**
     * Constants for {@link #TAG_GPS_DIFFERENTIAL}
     */
    @SuppressWarnings("unused")
    public static interface GpsDifferential {
        public static final short WITHOUT_DIFFERENTIAL_CORRECTION = 0;
        public static final short DIFFERENTIAL_CORRECTION_APPLIED = 1;
    }

    /**
     * Constants for the jpeg process algorithm used.
     *
     * @see #getJpegProcess()
     */
    @SuppressWarnings("unused")
    public static interface JpegProcess {
        public static final short BASELINE = (short) 0xFFC0;
        public static final short EXTENDED_SEQUENTIAL = (short) 0xFFC1;
        public static final short PROGRESSIVE = (short) 0xFFC2;
        public static final short LOSSLESS = (short) 0xFFC3;
        public static final short DIFFERENTIAL_SEQUENTIAL = (short) 0xFFC5;
        public static final short DIFFERENTIAL_PROGRESSIVE = (short) 0xFFC6;
        public static final short DIFFERENTIAL_LOSSLESS = (short) 0xFFC7;
        public static final short EXTENDED_SEQ_ARITHMETIC_CODING = (short) 0xFFC9;
        public static final short PROGRESSIVE_AIRTHMETIC_CODING = (short) 0xFFCA;
        public static final short LOSSLESS_AITHMETIC_CODING = (short) 0xFFCB;
        public static final short DIFFERENTIAL_SEQ_ARITHMETIC_CODING = (short) 0xFFCD;
        public static final short DIFFERENTIAL_PROGRESSIVE_ARITHMETIC_CODING = (short) 0xFFCE;
        public static final short DIFFERENTIAL_LOSSLESS_ARITHMETIC_CODING = (short) 0xFFCF;
    }

    /**
     * Constants for the {@link #TAG_SENSITIVITY_TYPE} tag
     */
    @SuppressWarnings("unused")
    public static interface SensitivityType {

        public static final short UNKNOWN = 0;

        /**
         * Standard output sensitivity
         */
        public static final short SOS = 1;

        /**
         * Recommended exposure index
         */
        public static final short REI = 2;

        /**
         * ISO Speed
         */
        public static final short ISO = 3;

        /**
         * Standard output sensitivity and Recommended output index
         */
        public static final short SOS_REI = 4;

        /**
         * Standard output sensitivity and ISO speed
         */
        public static final short SOS_ISO = 5;

        /**
         * Recommended output index and ISO Speed
         */
        public static final short REI_ISO = 6;

        /**
         * Standard output sensitivity and Recommended output index and ISO Speed
         */
        public static final short SOS_REI_ISO = 7;
    }

    /**
     * Options for calling {@link #readExif(java.io.InputStream, int)}, {@link #readExif(byte[], int)},
     * {@link #readExif(String, int)}
     */
    public static interface Options {
        /**
         * Option bit to request to parse IFD0.
         */
        int OPTION_IFD_0 = 1;
        /**
         * Option bit to request to parse IFD1.
         */
        int OPTION_IFD_1 = 1 << 1;
        /**
         * Option bit to request to parse Exif-IFD.
         */
        int OPTION_IFD_EXIF = 1 << 2;
        /**
         * Option bit to request to parse GPS-IFD.
         */
        int OPTION_IFD_GPS = 1 << 3;
        /**
         * Option bit to request to parse Interoperability-IFD.
         */
        int OPTION_IFD_INTEROPERABILITY = 1 << 4;
        /**
         * Option bit to request to parse thumbnail.
         */
        int OPTION_THUMBNAIL = 1 << 5;
        /**
         * Option bit to request all the options
         */
        int OPTION_ALL = OPTION_IFD_0 ^ OPTION_IFD_1 ^ OPTION_IFD_EXIF ^ OPTION_IFD_GPS ^ OPTION_IFD_INTEROPERABILITY ^ OPTION_THUMBNAIL;
    }
}
